package general.socketmanager;

import java.io.DataOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JOptionPane;

public class cSocketManager implements iServerThreadListener, iClientThreadListener {
	
	private List<iSocketListener> oListener;
	
	private List<cSocket> lSockets;
	
	public cSocketManager() {
		oListener = new ArrayList<iSocketListener>();
		lSockets = new ArrayList<cSocket>();
	}
	
	protected void finalize() throws Throwable {
		//do finalization here
		stop_AllThreads();
		close_AllSockets();
		super.finalize();
	}
	
	// Adds a listener to the monitor the sockets
	public void AddListener(iSocketListener oListener) {
		this.oListener.add(oListener);
	}
	
	// Creates a new managed listening socket
	public int NewServerSocket(final int iPort) {
		try {
			// create server socket
			ServerSocket oSocket = new ServerSocket(iPort);
			cSocket oTempSocket = new cSocket();
			// search free ID
			oTempSocket.iID = get_FreeSocketID();
			if (oTempSocket.iID > -1) {
				oTempSocket.oSocket = oSocket;
				oTempSocket.eType = cSocket.Type.Server;
				// create server thread
				oTempSocket.tThread = new Thread(new cServerThread(oTempSocket.iID, (ServerSocket)oTempSocket.oSocket, this));
				// start thread
				oTempSocket.tThread.start();
				// add to sockets list
				lSockets.add(oTempSocket);
				events_Fire_OnServer_Listening(oTempSocket.iID);
				return oTempSocket.iID;
			} else {
				oSocket.close();
				return -1;
			}
		}
		catch (Exception ex) {
			// Error: network error
			return -1;
		}
	}
	
	// Connects to a server with ip and port and creates a new client socket
	public int NewClientSocket(String sIP, int iPort) {
		try {
			// create socket / connect
			Socket oSocket = new Socket(sIP, iPort);
			cSocket oTempSocket = new cSocket();
			// search free ID
			oTempSocket.iID = get_FreeSocketID();
			if (oTempSocket.iID > -1) {
				oTempSocket.oSocket = oSocket;
				oTempSocket.eType = cSocket.Type.Client;
				oTempSocket.oOOS = new ObjectOutputStream(oSocket.getOutputStream());	
				oTempSocket.oOIS = new ObjectInputStream(oSocket.getInputStream());
				// create server thread
				oTempSocket.tThread = new Thread(new cClientThread(oTempSocket.iID, (Socket)oTempSocket.oSocket, this, oTempSocket.oOIS, oTempSocket.oOOS));
				// start thread
				oTempSocket.tThread.start();
				// add to sockets list
				lSockets.add(oTempSocket);
				events_Fire_OnClient_ConnectionEstablished(oTempSocket.iID);
				return oTempSocket.iID;
			} else {
				oSocket.close();
				return -1;
			}
		}
		catch (Exception ex) {
			// Error: host not reachable / network error
			return -1;
		}
	}
	
	// Sends data over a socket identified by a given id
	public boolean SendData(int iID, String sData) {
		cSocket oTempSocket = get_SocketByID(iID);
		
		if (oTempSocket.eType == cSocket.Type.Client) {
			if (oTempSocket.oSocket != null) {
				try {
					byte[] oByte = sData.getBytes();
					DataOutputStream dOut = new DataOutputStream(((Socket)oTempSocket.oSocket).getOutputStream());
					dOut.writeInt(oByte.length); // write length of the message
					dOut.write(oByte);           // write the message
					dOut.close();
					return true;
				}
				catch (Exception ex) {
					
				}

			}
		} else if (oTempSocket.eType == cSocket.Type.Server) {

		}
		

		return false;
	}
	
	// Sends data over a socket identified by a given id
	public boolean SendObject(int iID, Object oObject) {
		cSocket oTempSocket = get_SocketByID(iID);
		

		if (oTempSocket.eType == cSocket.Type.Client) {
			if (oTempSocket.oSocket != null) {
					try {
						oTempSocket.oOOS.writeObject(oObject);
						return true;
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}  

			}
		} else if (oTempSocket.eType == cSocket.Type.Server) {

		}
		

		return false;
	}
	
	// Disconnects a socket identified by a given id;
	public boolean DisconnectSocket(int iID) {
		cSocket oTempSocket = get_SocketByID(iID);
		
		if (oTempSocket.oSocket != null) {
			try {
				if (oTempSocket.eType == cSocket.Type.Server) {
					((ServerSocket)oTempSocket.oSocket).close();
				} else if (oTempSocket.eType == cSocket.Type.Client) {
					((Socket)oTempSocket.oSocket).close();
				}
				if (remove_SocketByID(iID)) {
					//events_Fire_OnClient_ConnectionClosed(iID);
					return true;
				}
			}
			catch (Exception ex) {
				if (remove_SocketByID(iID)) {
					//events_Fire_OnClient_ConnectionClosed(iID);
					return true;
				}
			}
		} else {
			if (remove_SocketByID(iID)) {
				events_Fire_OnClient_ConnectionClosed(iID);
				return true;
			}
		}
		return false;
	}
	
	
	////////////////////////////////////////////////////////////
	//////////  Private Class Functions
	////////////////////////////////////////////////////////////
	
	// Closes all open Sockets;
	private void close_AllSockets() {
		for (int i = 0; i < lSockets.size(); i++) {
			if (lSockets.get(i).eType == cSocket.Type.Server) {
				try {
					((ServerSocket)lSockets.get(i).oSocket).close();
				}
				catch (Exception ex) { }
				
			} else {
				if (((Socket)lSockets.get(i).oSocket).isConnected()) {
					try {
						((Socket)lSockets.get(i).oSocket).close();
					}
					catch (Exception ex) { }
				}
			}
		}
	}
	
	// Stops all running threads
	private void stop_AllThreads() {
		for (int i = 0; i < lSockets.size(); i++) {
			if (lSockets.get(i).tThread != null) {
				lSockets.get(i).tThread.stop();
			}
		}
	}
	
	// Returns a free ID, -1 if no free IDs are left
	private int get_FreeSocketID() {
		for (int i = 1001; i < 9999; i++) {
			if (!get_IsIDInUse(i)) {
				return i;
			}
		}
		return -1;
	}
	
	// Returns true if iID is already in use, false otherwise
	private boolean get_IsIDInUse(int iID) {
		for (int i = 0; i < lSockets.size(); i++) {
			if (lSockets.get(i).iID == iID) {
				return true;
			}
		}
		return false;
	}
	
	// Returns the socket identified by a given id
	private cSocket get_SocketByID(int iID) {
		for (int i = 0; i < lSockets.size(); i++) {
			if (lSockets.get(i).iID == iID) {
				return lSockets.get(i);
			}
		}
		return null;
	}
	
	// Removes a managed socket identified by a given id from the list
	private boolean remove_SocketByID(int iID) {
		for (int i = 0; i < lSockets.size(); i++) {
			if (lSockets.get(i).iID == iID) {
				lSockets.remove(i);
				return true;
			}
		}
		return false;
	}
	
	// Fires all OnServer_NewClient() events
	private void events_Fire_OnServer_Listening(int iSocketID) {
		for (int i = 0; i < oListener.size(); i++) {
			oListener.get(i).OnServer_Listening(iSocketID);
		}
	}
	
	// Fires all OnServer_NewClient() events
	private void events_Fire_OnServer_NewClient(int iServerSocketID, int iNewClientSocketID) {
		for (int i = 0; i < oListener.size(); i++) {
			oListener.get(i).OnServer_NewClient(iServerSocketID, iNewClientSocketID);
		}
	}
	
	// Fires all OnClient_ConnectionEstablished() events
	private void events_Fire_OnClient_ConnectionEstablished(int iSocketID) {
		for (int i = 0; i < oListener.size(); i++) {
			oListener.get(i).OnClient_ConnectionEstablished(iSocketID);
		}
	}
	
	// Fires all OnClient_DataReceived() events
	private void events_Fire_OnClient_DataReceived(int iSocketID, byte[] aBytes) {
		for (int i = 0; i < oListener.size(); i++) {
			oListener.get(i).OnClient_DataReceived(iSocketID, aBytes);
		}
	}
	
	// Fires all OnClient_ObjectReceived() events
	private void events_Fire_OnClient_ObjectReceived(int iSocketID, Object oObject) {
		for (int i = 0; i < oListener.size(); i++) {
			oListener.get(i).OnClient_ObjectReceived(iSocketID, oObject);
		}
	}
	
	// Fires all OnClient_ConnectionClosed() events
	private void events_Fire_OnClient_ConnectionClosed(int iSocketID) {
		for (int i = 0; i < oListener.size(); i++) {
			oListener.get(i).OnClient_ConnectionClosed(iSocketID);
		}
	}

	////////////////////////////////////////////////////////////
	//////////  Implemented Interfaces
	////////////////////////////////////////////////////////////
	
	@Override
	public void OnNewConnection(int iID, Socket oSocket) {
		cSocket oTempSocket = new cSocket();
		
		oTempSocket.iID = get_FreeSocketID();
		try {
			if (oTempSocket.iID > -1) {
				oTempSocket.oSocket = oSocket;
				oTempSocket.eType = cSocket.Type.Client;
				oTempSocket.oOOS = new ObjectOutputStream(oSocket.getOutputStream());
				oTempSocket.oOIS = new ObjectInputStream(oSocket.getInputStream());
				// create server thread
				oTempSocket.tThread = new Thread(new cClientThread(oTempSocket.iID, (Socket)oTempSocket.oSocket, this, oTempSocket.oOIS, oTempSocket.oOOS));
				// start thread
				oTempSocket.tThread.start();
				// add to sockets list
				lSockets.add(oTempSocket);
				events_Fire_OnServer_NewClient(iID, oTempSocket.iID);
				events_Fire_OnClient_ConnectionEstablished(oTempSocket.iID);
			
			} else {
			// Error adding socket to managed list, disconnecting

				oSocket.close();
			}
		}
		catch (Exception ex) {
				
		}
	}

	@Override
	public void OnDataReceived(int iID, byte[] aBytes) {
		events_Fire_OnClient_DataReceived(iID, aBytes);
	}

	@Override
	public void OnObjectReceived(int iID, Object oObject) {
		events_Fire_OnClient_ObjectReceived(iID, oObject);
	}

	@Override
	public void OnConnectionClosed(int iID) {
		events_Fire_OnClient_ConnectionClosed(iID);
	}


	
}
